<?php

/**
 *
 * @author ben<lovebing365@qq.com>
 */
class HTTPClient {

    private $timeout = 0;
    private $methods = array('GET', 'POST', 'PUT', 'DELETE', 'HEAD');
    private $options = array('http' => array('header' => array()), 'ssl' => array());
    private $header = array();
    private $responseHeaders = array();
    private static $instance = null;
    private $errno = 0;
    private $errstr = '';
    private $url = '';
    private $autoHost = true;
    private $cookie = array();
    private $isProxy = false;

    /**
     * 
     * @param type $timeout
     */
    public function __construct($timeout = 10) {
        $this->setTimeout($timeout);
    }

    public static function getInstance($timeout = 10) {
        if (is_null(self::$instance)) {
            self::$instance = new self($timeout);
        }
        return self::$instance;
    }

    public function setOneHeader($name, $value) {
        $this->header[$name] = $value;
    }

    public function setOneCookie($name, $value) {
        $this->cookie[$name] = $value;
    }

    public function setSSLOptions($localCert, $cafile = '', $CNMatch = '') {
        $options = array(
            'verify_peer' => true,
            'local_cert' => $localCert,
            'verify_depth' => 5
        );
        if ($cafile) {
            $options['cafile'] = $cafile;
        }
        if ($cafile) {
            $options['CN_match'] = $CNMatch;
        }

        $this->options['ssl'] = $options;
    }

    public function clearSSLOptions() {
        $this->options['ssl'] = array();
    }

    private function setHeader($headers) {
        $this->options['http']['header'] = $headers;
    }

    public function setMethod($method) {
        $this->options['http']['method'] = $method;
    }

    public function setBody($body) {
        $this->options['http']['content'] = $body;
    }

    public function setFollowLocation($flag) {
        $this->options['http']['follow_location'] = $flag ? 1 : 0;
    }

    public function setAutoHost($flag) {
        $this->autoHost = $flag ? true : false;
    }

    public function getCookie() {
        return $this->cookie;
    }

    public function setContentType($contentType = null) {
        if (is_null($contentType)) {
            unset($this->header['Content-type']);
            return;
        }
        $this->header['Content-type'] = $contentType;
    }

    private function buildCookie() {
        $arr = array();
        foreach ($this->cookie as $name => $value) {
            $arr[] = $name . '=' . $value;
        }
        $this->setOneHeader('Cookie', implode(';', $arr));
    }

    public function buildHeader() {
        $this->setHeader(implode("\r\n", $this->getRequestHeader()));
    }

    private function saveCookie() {
        $cookies = $this->getResponseHeaderItem('Set-Cookie');
        foreach ($cookies as $k => $v) {
        	
            $str = explode(';', $v);
            $str = $str[0];

            $pos = strpos($str, '=');
            if ($pos !== false && $pos > 0) {
                $this->setOneCookie(substr($str, 0, $pos), substr($str, $pos + 1));
            }
        }
    }

    public function getRequestHeader() {
        $item = array();
        foreach ($this->header as $k => $v) {
            $item[] = $k . ': ' . $v;
        }
        return $item;
    }

    private function filterContextOptions(array $options) {
        foreach ($options as $k => $v) {
            if (empty($v)) {
                unset($options[$k]);
            }
        }
        return $options;
    }

    /**
     * 
     * @param type $type
     * @param type $content
     * @return type
     */
    private function createContext($type = '', $content = '') {
        if (!$this->isProxy) {
            switch ($type) {
                case $this->methods[1]:
                case $this->methods[2]:
                    $this->setContentType('application/x-www-form-urlencoded');
                    $this->setOneHeader('Content-Length', strlen($content));
                    break;
                default :
                    $this->setContentType(null);
            }
            if ($this->autoHost) {
                $this->setHostFromUrl();
            }

            $this->buildCookie();
        }
        $this->buildHeader();
        $this->setMethod($type);
        $this->setBody($content);


        return stream_context_create($this->filterContextOptions($this->options));
    }

    public function setHostFromUrl() {
        $arr = parse_url($this->url);
        if (isset($arr['host'])) {
            $this->setOneHeader('Host', $arr['host']);
        }
    }

    /**
     * 
     * @param type $timeout
     */
    public function setTimeout($timeout) {
        $this->timeout = $timeout;
        $this->options['http']['timeout'] = $timeout;
    }

    /**
     * 
     * @param type $url
     * @param array $data
     * @return type
     */
    public function get($url, array $data = array()) {
        if (!empty($data)) {
            $params = http_build_query($data);
            if (!strpos($url, '?')) {
                $url .= '?';
            }
            $url .= $params;
        }

        $context = $this->createContext($this->methods[0]);
        $this->url = $url;
        return $this->request($url, $context);
    }

    /**
     * 
     * @param type $url
     * @param array $data
     * @return type
     */
    public function post($url, array $data) {
        $context = $this->createContext($this->methods[1], http_build_query($data));
        $this->url = $url;
        return $this->request($url, $context);
    }
    
/**
 * 发送双向认证的数据
 * @name ssl Curl Post数据
 * @param string $url 接收数据的api
 * @param string $vars 提交的数据
 * @param array $pem 认证文件的路径 
 * @param string $key 认证密码
 * @param int $second 要求程序必须在$second秒内完成,负责到$second秒后放到后台执行
 * @return string or boolean 成功且对方有返回值则返回
 */
	public function post_ssl($url, $vars, Array $pem, $key,$second=30,$aHeader=array("content-type: application/x-www-form-urlencoded; 
charset=UTF-8"))
	{
		if(!$key){
			return 'no key';
		}
		$cert = $pem['cert'];
		$cert_key = $pem['cert_key'];
		$rootca = $pem['rootca'];
		$ch = curl_init();
		//curl_setopt($ch,CURLOPT_VERBOSE,'1');
		curl_setopt($ch,CURLOPT_TIMEOUT,$second);
		curl_setopt($ch,CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($ch,CURLOPT_URL,$url);
		curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
		curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,false);
		curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
		curl_setopt($ch,CURLOPT_SSLCERT,$cert);
		curl_setopt($ch,CURLOPT_SSLCERTPASSWD,$key);
		curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
		curl_setopt($ch,CURLOPT_SSLKEY,$cert_key);
		curl_setopt($ch,CURLOPT_CAINFO,$rootca); 
		if( count($aHeader) >= 1 ){
			curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);
		}
		
		curl_setopt($ch,CURLOPT_POST, 1);
		curl_setopt($ch,CURLOPT_POSTFIELDS,$vars);
		$data = curl_exec($ch);
		
                
		if($data){
                       // print_r($data);
                        curl_close($ch);
			return $data;
		}else{
                        $error = curl_errno($ch);
                        echo "call faild, errorCode:$error\n"; 
                        Yii::log('curl_errno:'.$error,'error');
                        curl_close($ch);
			return false;
		}
	}

    public function postXML($url, $XML) {
    	$context = $this->createContext($this->methods[1], $XML);
    	$this->url = $url;
    	return $this->request($url, $context);
    }
    /**
     * 
     * @param type $url
     * @param type $text
     * @return type
     */
    public function postText($url, $text) {
        $context = $this->createContext($this->methods[1], $text);
        $this->url = $url;
        return $this->request($url, $context);
    }

    public function head($url) {
        $context = $this->createContext($this->methods[4]);
        $this->url = $url;
        return $this->request($url, $context);
    }

    public function getResponseHeader() {
        return $this->responseHeaders;
    }

    public function getResponseHeaderItem($name) {
        $headers = $this->getResponseHeader();
        $res = array();
        foreach ($headers as $item) {
            $pos = strpos($item, ':');
            if ($pos !== false && $pos > 0 && substr($item, 0, $pos) == $name) {
                $res[] = trim(substr($item, $pos + 1));
            }
        }
        return $res;
    }

    private function request($url, $context) {
        $this->setError(0, '');
        $res = file_get_contents($url, false, $context);
        if ($res === false) {
            $error = error_get_last();
            $this->setError($error['type'], $error['message']);
        }
        $this->responseHeaders = $http_response_header;
        $this->saveCookie();
        return $res;
    }

    private function setError($errno = 0, $errstr = '') {
        $this->errno = $errno;
        $this->errstr = $errstr;
    }

    public function getErrorCode() {
        return $this->errno;
    }

    public function getErrorMessage() {
        return $this->errstr;
    }

    public function getAllClientheaders() {
        $h = array('CONTENT_LENGTH', 'CONTENT_TYPE');
        $headers = array();
        foreach ($_SERVER as $name => $value) {
            if (in_array($name, $h)) {
                $headers[str_replace('_', '-', ucwords(strtolower($name)))] = $value;
                continue;
            }
            if (substr($name, 0, 5) == 'HTTP_') {
                $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
            }
        }
        return $headers;
    }

    public function proxy($url) {
        $this->url = $url;
        $method = $_SERVER['REQUEST_METHOD'];

        $this->isProxy = true;
        $headers = $this->getAllClientheaders();
        $this->header = $headers;
        $this->setHostFromUrl();
        switch ($method) {
            case 'POST':
            case 'PUT':
                $data = file_get_contents('php://input');
                $context = $this->createContext($method, $data);
                break;
            default :
                $context = $this->createContext($method);
        }
        $res = $this->request($url, $context);
        $resHeaders = $this->getResponseHeader();
        foreach ($resHeaders as $item) {
            header($item);
        }
        return $res;
    }

    /**
     * 上传文件
     * @param string $url
     * @param string $file
     * @param string $fileFieldName
     * @param string $timeout
     * @param string &$error
     * @return string
     * @throws RuntimeException
     */
    public static function uploadFile($url, $file, $fileFieldName = 'media', $timeout = null, &$error = '') {
        if (!is_file($file)) {
            throw new RuntimeException("文件{$file}不在存");
        }
        if ($timeout == null) {
            $timeout = 10;
        }
        $cfile = curl_file_create($file, self::getMime($file));

        $data = array($fileFieldName => $cfile);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        $ret = curl_exec($ch);
        if ($ret === false) {
            $error = curl_error($ch);
        }
        curl_close($ch);
        return $ret;
    }

    /**
     * 获取文件mime
     * @param string $file
     * @return string
     */
    public static function getMime($file) {
        if (!is_file($file)) {
            return '';
        }
        $finfo = new finfo(FILEINFO_MIME);
        $mime = $finfo->file($file);
        return $mime;
    }

    /**
     * 
     * @param type $disposition
     * @return type
     */
    public static function getFileNameFromDisposition($disposition) {
        $pos = strpos($disposition, ';');
        $fileinfo = substr($disposition, $pos + 1);

        $pos2 = strpos($fileinfo, '=');
        $file = substr($fileinfo, $pos2 + 1);

        if (strpos($file, '"') === 0) {
            $file = substr($file, 1);
        }
        $len = strlen($file);
        if (strpos($file, '"') === $len - 1) {
            $file = substr($file, 0, $len - 1);
        }
        return $file;
    }

}
